﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Browser;

namespace DraggableWindow
{
	
	[TemplatePart(Name = DragDropControl.RootElement, Type = typeof(Canvas))]
	[TemplatePart(Name = DragDropControl.ContainerElement, Type = typeof(Panel))]
	[TemplatePart(Name = DragDropControl.ContentElement, Type = typeof(FrameworkElement))]
	public class DragDropControl : ContentControl
	{
        private const string RootElement = "Root";
		private const string ContainerElement = "Container";
		private const string ContentElement = "Content";
		#region Fields

		Point _oldPoint;
		bool _mouseDown = false;
        protected ResizeDirection ResizeDirection;

		protected Canvas Root;
		protected Panel Container;
        TranslateTransform transform;

		#endregion

		public event EventHandler PositionChanged;
		public event EventHandler Resized;

		#region Constructor
		public DragDropControl()
		{
            this.DefaultStyleKey = typeof(DragDropControl);
			this.SetValue(Canvas.ZIndexProperty, DragDropControl.MaxZIndex);
			DragDropControl.MaxZIndex++;
            transform = new TranslateTransform();
           
            this.MouseLeftButtonDown += DragDropControl_MouseLeftButtonDown;
            this.MouseLeftButtonUp += DragDropControl_MouseLeftButtonUp;
            this.MouseMove += DragDropControl_MouseMove;

		}
            
		#endregion		

        #region IsResizing

        /// <summary>
        /// Identifies the <see cref="IsResizing"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty IsResizingProperty =
            DependencyProperty.Register("IsResizing", typeof(bool), typeof(DragDropControl),
              new PropertyMetadata(
                new PropertyChangedCallback(OnIsResizingChanged)
              ));

        /// <summary>
        /// Gets or sets the IsResizing property.  This dependency property 
        /// indicates if we are resizing.
        /// </summary>
        public bool IsResizing
        {
            get { return (bool)GetValue(IsResizingProperty); }
            set { SetValue(IsResizingProperty, value); }
        }

        /// <summary>
        /// Handles changes to the IsResizing property.
        /// </summary>
        private static void OnIsResizingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ( (DragDropControl)d ).OnIsResizingChanged(e);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the IsResizing property.
        /// </summary>
        protected virtual void OnIsResizingChanged(DependencyPropertyChangedEventArgs e)
        {
        }

        #endregion



		#region MaxZIndex
		/// <summary>
		/// Identifies the MaxZIndex dependency property.
		/// </summary>		
		public static int MaxZIndex;
		#endregion MaxZIndex

		#region CanResize
		/// <summary>
		/// Identifies the CanResize dependency property.
		/// </summary>
		public static readonly DependencyProperty CanResizeProperty = DependencyProperty.Register("CanResize", typeof(bool), typeof(DragDropControl), null);

		/// <summary>
        /// Enables or disables the ability to resize this control
		/// </summary>
        public bool CanResize
		{
			get { return (bool)GetValue(CanResizeProperty); }
			set { SetValue(CanResizeProperty, value); }
		}
		#endregion CanResize

        #region OffsetX
        /// <summary>
        /// Identifies the OffsetX dependency property.
        /// </summary>
        public static readonly DependencyProperty OffsetXProperty =
        DependencyProperty.Register(
        "OffsetX",
        typeof(double),
        typeof(DragDropControl),
        null);

        /// <summary>
        /// Gets or sets the OffsetX possible Value of the int object.
        /// </summary>
        public double OffsetX
        {
            get { return (double)GetValue(OffsetXProperty); }
            set 
            { 
                SetValue(OffsetXProperty, value);
                this.transform.X = value;
            }
        }       
        #endregion OffsetX

        #region OffsetY
        /// <summary>
        /// Identifies the OffsetY dependency property.
        /// </summary>
        public static readonly DependencyProperty OffsetYProperty =
        DependencyProperty.Register(
        "OffsetY",
        typeof(double),
        typeof(DragDropControl),
        null);

        /// <summary>
        /// Gets or sets the OffsetY possible Value of the int object.
        /// </summary>
        public double OffsetY
        {
            get { return (double)GetValue(OffsetYProperty); }
            set 
            { 
                SetValue(OffsetYProperty, value); 
                this.transform.Y = value; 
            }
        }       
        #endregion OffsetY

       

        

         
		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();
            Root = GetTemplateChild(RootElement) as Canvas;
            Container = GetTemplateChild(ContainerElement) as Panel;
            if (Container == null)
                throw new PopupException(ContainerElement + " is Missing in the template");
            this.RenderTransform = transform;
		}

        protected override Size ArrangeOverride(Size finalSize)
        {
            Debug.WriteLine("Hello world");
            
            Size resultSize = base.ArrangeOverride(finalSize);
            if ( Double.IsNaN(this.Width) )
                this.Width = this.Container.ActualWidth;
            if ( Double.IsNaN(this.Height) )
                this.Height = this.Container.ActualHeight;

            UIElement child = this.Content as UIElement;
            if ( child != null )
            {
                child.Arrange(new Rect(0, 0, child.DesiredSize.Width, child.DesiredSize.Height));
            }


            return resultSize;
        }
        //protected override Size MeasureOverride(Size availableSize)
        //{
        //    Debug.WriteLine("I like measure");
        //    Size resultSize = availableSize; //base.MeasureOverride(availableSize);
        //    UIElement child = this.Container as UIElement;
        //    if ( child != null )
        //    {
        //        child.Measure(availableSize);
        //        resultSize.Width = Math.Max(resultSize.Width, child.DesiredSize.Width);
        //        resultSize.Height = Math.Max(resultSize.Height, child.DesiredSize.Height);

        //    }
        //    resultSize.Width = double.IsPositiveInfinity(availableSize.Width) ?
        //        resultSize.Width : availableSize.Width;

        //    resultSize.Height = double.IsPositiveInfinity(availableSize.Height) ?
        //        resultSize.Height : availableSize.Height;

        //    return resultSize;
        //}
       

		#region Mouse Events

		void DragDropControl_MouseMove(object sender, MouseEventArgs e)
		{
			Point newPoint = e.GetPosition(null);
            //Make sure the Point is withing Application.Current.RootVisual
            Point p0 = Position.GetAbsolutePosition(Application.Current.RootVisual as FrameworkElement);
            Point p1 = Position.GetAbsolutePosition(Application.Current.RootVisual as FrameworkElement,
                Position.Corner.RightBottom);

            Point newOffset = ComputeOffset(ref newPoint);

            
			if (_mouseDown)
			{
                if (ResizeDirection == ResizeDirection.None ) // Moving the Panel
				{                   
                    if (this.OffsetX + newPoint.X - _oldPoint.X > p0.X &&
                        this.OffsetY + newPoint.Y - _oldPoint.Y > p0.Y &&
                        this.OffsetX + newPoint.X - _oldPoint.X + this.Width < p1.X &&
                        this.OffsetY + newPoint.Y - _oldPoint.Y + this.Height < p1.Y)
                    {

                        OnUpdateOffsetInternal(newOffset);
                        RaisePositionChanged();
                    }
				}
                else  // Resize
				{
                    OnUpdateSizeCommand(sender, e, newOffset );
                    //double widthDelta = 0;
                    //double heightDelta = 0;
                    
                    //if (newPoint.X > p0.X && newPoint.Y > p0.Y && newPoint.X < p1.X && newPoint.Y < p1.Y)
                    //{
                    //    // Resizing the Panel
                    //    Panel p = this.Parent as Panel;
                        
                    //    Point change = new Point(newPoint.X - _oldPoint.X, newPoint.Y - _oldPoint.Y);
                    //    UIElement c = this.Content as UIElement;
                    //    if (ResizeDirection == ResizeDirection.North  && this.Height - change.Y > 2 &&
                    //       (!Double.IsNaN(this.MaxHeight) && this.Height < this.MaxHeight && change.Y < 0 ||
                    //        !Double.IsNaN(this.MinHeight ) && this.Height > this.MinHeight && change.Y > 0 ) ) //North
                    //    {
                    //        heightDelta = this._resizeOrigin.Y - change.Y;

                    //        newOffset.Y  += change.Y;
                            
                    //        RaisePositionChanged();
                    //        RaiseResized();
                    //    }
                    //    if ( ResizeDirection == ResizeDirection.South && this.Height + change.Y > 2 &&
                    //       ( !Double.IsNaN(this.MaxHeight) && this.Height < this.MaxHeight && change.Y > 0 ||
                    //       !Double.IsNaN(this.MinHeight) && this.Height > this.MinHeight && change.Y < 0 ) ) //Sorth
                    //    {
                    //        heightDelta = this._resizeOrigin.Y + change.Y;
                            
                    //        RaiseResized();
                    //    }
                    //    if ( ResizeDirection == ResizeDirection.West && this.Width - change.X > 2 &&
                    //       ( !Double.IsNaN(this.MaxWidth) && this.Width < this.MaxWidth && change.X < 0 ||
                    //        !Double.IsNaN(this.MinWidth) && this.Width > this.MinWidth && change.X > 0 ) ) //West
                    //    {
                    //        newOffset.X += change.X;
                    //        widthDelta=this._resizeOrigin.X - change.X;
                            
                    //        RaisePositionChanged();
                    //        RaiseResized();
                    //    }
                    //    if ( ResizeDirection == ResizeDirection.East && this.Width + change.X > 2 &&
                    //       ( !Double.IsNaN(this.MaxWidth) && this.Width < this.MaxWidth && change.X > 0 ||
                    //         !Double.IsNaN(this.MinWidth) && this.Width > this.MinWidth && change.X < 0 ) ) //East
                    //    {
                    //        widthDelta = this._resizeOrigin.X + change.X;
                    //    }
                    //}
                    //this.OnUpdateSizeInternal(new Size(
                    //Math.Max(0, this._originalWidth + widthDelta),
                    //Math.Max(0, this._originalHeight + heightDelta)
                    //));
				}
                _oldPoint = newPoint;

			}
			else
			{
				// Check to see if mouse is on a resize area
				if (this.CanResize)
				{
                    ResizeHitTest(newPoint);
					SetCursor(ResizeDirection );
				}
			}
		}

        private Point ComputeOffset(ref Point newPoint)
        {
            Point newOffset = new Point(0, 0);
            newOffset.X = newPoint.X - _oldPoint.X;
            newOffset.Y = newPoint.Y - _oldPoint.Y;
            return newOffset;
        }
		
		void DragDropControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
		{
			if (_mouseDown)
			{

				this.Opacity = 1;
                
                this.ReleaseMouseCapture();
				_mouseDown = false;
                this.IsResizing = false;
			}
		}

		void DragDropControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			if (e.Handled)
				return;
            

			_mouseDown = true;
            this._oldPoint = e.GetPosition(null);
            this._resizeOrigin = e.GetPosition(Application.Current.RootVisual );
            this._originalWidth = this.ActualWidth;
            this._originalHeight = this.ActualHeight;
            this.Opacity *= 0.8;
            this.CaptureMouse();
            if ( ResizeHitTest(e.GetPosition(null)) == ResizeDirection.None )
            {
                // we are moving
            }
            else
            {
                this.IsResizing = true;
                // we are resizing

            }
            
			
			int zIndex = (int)this.GetValue(Canvas.ZIndexProperty);
			if (zIndex > DragDropControl.MaxZIndex)
				DragDropControl.MaxZIndex = zIndex + 1;
			else if (zIndex < DragDropControl.MaxZIndex)
				DragDropControl.MaxZIndex++;
			this.SetValue(Canvas.ZIndexProperty, DragDropControl.MaxZIndex);
		}

		#endregion

		#region Helper Methods

  
		private ResizeDirection ResizeHitTest(Point pt)
		{
			double x0 = pt.X;
			double y0 = pt.Y;

			Point P = Position.GetAbsolutePosition(Container);

			double x1 = P.X;
			double y1 = P.Y;
			double x2 = x1 + Container.ActualWidth;
			double y2 = y1 + Container.ActualHeight;

			// Corners
            if ( Math.Abs(x0 - x1) < 6 && Math.Abs(y0 - y1) < 6 )
                ResizeDirection = ResizeDirection.NorthWest;

            else if ( Math.Abs(x0 - x1) < 6 && Math.Abs(y2 - y0) < 6 )
                ResizeDirection = ResizeDirection.SouthWest;

            else if ( Math.Abs(x2 - x0) < 6 && Math.Abs(y2 - y0) < 6 )
                ResizeDirection = ResizeDirection.SouthEast;

            else if ( Math.Abs(x2 - x0) < 6 && Math.Abs(y0 - y1) < 6 )
                ResizeDirection = ResizeDirection.NorthEast;

            // Sides
            else if ( Math.Abs(y0 - y1) < 4 )
                ResizeDirection = ResizeDirection.North;
            else if ( Math.Abs(x2 - x0) < 4 )
                ResizeDirection = ResizeDirection.East;
            else if ( Math.Abs(y2 - y0) < 4 )
                ResizeDirection = ResizeDirection.South;
            else if ( Math.Abs(x0 - x1) < 4 )
                ResizeDirection = ResizeDirection.West;
            else
                ResizeDirection = ResizeDirection.None;

            return ResizeDirection;
		}

        void SetCursor(ResizeDirection resize)
		{
			if (resize == ResizeDirection.North  || resize== ResizeDirection.South )
				Cursor = Cursors.SizeNS;
			else if (resize == ResizeDirection.West  || resize == ResizeDirection.East )
				Cursor = Cursors.SizeWE;
			else
				Cursor = Cursors.Arrow;
		}
		void RaiseResized()
		{
			if (this.Resized != null)
				Resized(this, null);
		}
		void RaisePositionChanged()
		{
			if (this.PositionChanged != null)
				PositionChanged(this, null);
		}

		#endregion

        
        private Point _resizeOrigin;
        private double _originalWidth;
        private double _originalHeight;

        /// <summary>
        /// Called before we change the size...
        /// </summary>
        /// <param name="finalSize">The size we are going to use</param>
        protected virtual void OnPreviewChangeSize(Size finalSize)
        {

        }



        /// <summary>
        /// Provide any size adjustments before actually resizing...
        /// </summary>
        /// <param name="desiredSize">The size we'd like to go with</param>
        /// <returns>The size we should actually use</returns>
        protected virtual Size AdjustSize(Size desiredSize)
        {
            return desiredSize;
        }
        private void OnUpdateSizeInternal(Size desiredSize)
        {

            Size actualSize = this.AdjustSize(desiredSize);
          
            if ( !actualSize.Equals(new Size(this.Width, this.Height)) )
            {


                this.OnPreviewChangeSize(actualSize);
                this.Width = actualSize.Width;
                this.Height = actualSize.Height;
            }
           
        }

        private void OnUpdateSizeCommand(object sender, MouseEventArgs e, Point newOffset)
        {
        
            if ( this != null )
            {
                // get the point relative to the whole damn thing...
                Point point = e.GetPosition(Application.Current.RootVisual); // resizer._frameworkElement.PointToScreen(Mouse.GetPosition(resizer._frameworkElement));
                Point oPoint = e.GetPosition(null);
                double widthDelta = 0;
                double heightDelta = 0;
                Point offsetDelta = new Point(0,0);
                
                switch ( this.ResizeDirection )
                {
                    case ResizeDirection.NorthEast:
                        widthDelta = point.X - this._resizeOrigin.X;
                        heightDelta = this._resizeOrigin.Y - point.Y;
                        break;
                    case ResizeDirection.NorthWest:
                        widthDelta = this._resizeOrigin.X - point.X;
                        heightDelta = this._resizeOrigin.Y - point.Y;
                        break;
                    case ResizeDirection.SouthEast:
                        widthDelta = point.X - this._resizeOrigin.X;
                        heightDelta = point.Y - this._resizeOrigin.Y;
                        break;
                    case ResizeDirection.SouthWest:
                        widthDelta = this._resizeOrigin.X - point.X;
                        heightDelta = point.Y - this._resizeOrigin.Y;
                        break;
                    case ResizeDirection.North:
                        heightDelta = this._resizeOrigin.Y - point.Y;
                        offsetDelta.Y =  newOffset.Y;
                        break;
                    case ResizeDirection.South:
                        heightDelta = point.Y - this._resizeOrigin.Y;
                        //offsetDelta.Y = this._resizeOrigin.Y - point.Y;
                        break;
                    case ResizeDirection.East:
                        widthDelta = point.X - this._resizeOrigin.X;
                        
                        break;
                    case ResizeDirection.West:
                        widthDelta = point.X - this._resizeOrigin.X;
                        widthDelta = -widthDelta;
                        offsetDelta.X = newOffset.X;
                        break;
                    default:
                        Debug.WriteLine("Unexpected ResizeDirection: " + this.ResizeDirection);
                        break;
                }

                this.OnUpdateSizeInternal(new Size(
                    Math.Max(0, this._originalWidth + widthDelta),
                    Math.Max(0, this._originalHeight + heightDelta)
                    ));

                this.OnUpdateOffsetInternal(offsetDelta);

            }


            
        }

        /// <summary>
        /// Provide any offset adjustments before actually doing it...
        /// </summary>
        /// <param name="desiredOffset">The offset we'd like to go with</param>
        /// <returns>The size we should actually use</returns>
        protected virtual Point AdjustOffset(Point desiredOffset)
        {
            return desiredOffset;
        }
        private void OnUpdateOffsetInternal(Point desiredOffset)
        {

            Point actualOffset = this.AdjustOffset(desiredOffset);

            if ( !actualOffset.Equals(new Point(this.OffsetX, this.OffsetY)) )
            {


                this.OnPreviewChangeOffset(actualOffset);
                this.OffsetX += actualOffset.X;
                this.OffsetY += actualOffset.Y;
                
            }

        }

        /// <summary>
        /// Called before we change the offset...
        /// </summary>
        /// <param name="finalOffset">The offset we are going to use</param>
        protected virtual void OnPreviewChangeOffset(Point finalOffset)
        {

        }


	}		
}
